import { CloseCircleFilled } from '@ant-design/icons';
import { Popover, Result, Typography } from 'antd';
import { GlobalToken } from 'antd/es/theme/interface';
import lodash from 'lodash';
import React from 'react';
import * as Components from './components';
import { DynamicSchema } from './index';

export class ReactNodeRenderError extends Error {
  fieldPath: string;
  schema: object | string;

  constructor(message: string, fieldPath: string, schema: object | string) {
    super(message);
    this.fieldPath = fieldPath;
    this.schema = schema;
  }
}

const isJSFunction = (data: { type: string }) => {
  if (!lodash.isObject(data)) {
    return false;
  }

  return data.type === 'JSFunction';
};

const isJSExpression = (data: { type: string }) => {
  if (!lodash.isObject(data)) {
    return false;
  }

  return data.type === 'JSExpression';
};

const isJSSlot = (data: { type: string }) => {
  if (!lodash.isObject(data)) {
    return false;
  }

  return data.type === 'JSSlot';
};

function runFunction<T extends any[]>(valueEnum: any, ...rest: T) {
  if (typeof valueEnum === 'function') {
    return valueEnum(...rest);
  }
  return valueEnum;
}

function arraysToObject(keys: string[], values: any[]) {
  return keys.reduce((obj, key, index) => {
    // @ts-ignore
    obj[key] = values[index];
    return obj;
  }, {});
}

const ddeRender = {
  reactPropsRender: (
    token: GlobalToken,
    fieldPath: string,
    propsProps: Record<string, any>,
    dcExtParams: Record<string, any>,
    restProps?: Record<string, any> | any,
    key?: string,
  ): any => {
    console.debug('[DC] 一、渲染的propsProps:', propsProps, dcExtParams);

    // 1. 转换 props 开始
    const dynamicComponentRender = (
      fieldPath: string,
      schema: any | any[],
      dcExtParams: any,
    ): any => {
      console.debug('[DC] dynamicComponentRender', schema, dcExtParams);
      return ddeRender.reactNodeRender(token, fieldPath, schema, dcExtParams);
    };

    const generateJsSlot = (
      fieldPath: string,
      typeValue: Record<string, any>,
      dcExtParams: Record<string, any>,
    ): any => {
      console.debug('[DC] 1.generateJsSlot: ', typeValue.value, dcExtParams);
      const slotObject = typeValue.value;
      const params = typeValue.params;
      // const { dcExtParams } = option;

      // 如果 params 不为空，说明插槽需要函数
      if (params) {
        return function (...restParams: any[]) {
          // @ts-ignore
          console.debug('[DC] JSSlot function', restParams, this, dcExtParams);
          return dynamicComponentRender(fieldPath, slotObject, {
            ...dcExtParams,
            ...arraysToObject(params, { ...[...restParams] }),
          });
        };
      }

      // 处理插槽是对象
      return ddeRender.reactNodeRender(
        token,
        fieldPath,
        slotObject,
        dcExtParams,
      );
    };

    const generateJsFunction = (
      fieldPath: string,
      typeValue: Record<string, any>,
      dcExtParams: Record<string, any>,
      props: Record<string, any>,
    ): any => {
      console.debug(
        '[DC] 1.generateJsFunction: ',
        typeValue,
        props,
        dcExtParams,
      );
      const functionStr = typeValue.value;

      try {
        return runFunction(new Function('', `return ${functionStr};`)).bind({
          props,
          ...dcExtParams,
        });
      } catch (e) {
        throw new ReactNodeRenderError(
          `JSFunction 解析错误。${e}`,
          fieldPath,
          typeValue,
        );
      }
    };

    const generateJsExpression = (
      fieldPath: string,
      typeValue: Record<string, any>,
      dcExtParams: Record<string, any>,
      props?: Record<string, any>,
    ): any => {
      console.debug('[DC] 1.generateJsExpression: ', typeValue, dcExtParams);
      const functionStr = typeValue.value;

      try {
        if (lodash.startsWith(functionStr, 'function')) {
          return runFunction(new Function('', `return ${functionStr};`)).call({
            props,
            ...dcExtParams,
          });
        }

        return runFunction(
          new Function('', `return ${functionStr};`).bind({
            props,
            ...dcExtParams,
          }),
        );
      } catch (e) {
        throw new ReactNodeRenderError(
          `JsExpression 解析错误。${e}`,
          fieldPath,
          typeValue,
        );
      }
    };

    // 支持 props 为 JSExpression 的转换
    if (isJSExpression(propsProps as any)) {
      return generateJsExpression(fieldPath, propsProps, dcExtParams);
    }

    // if (lodash.isEmpty(componentName)) {
    //   throw new ReactNodeRenderError(
    //     `字段 componentName 不能为空`,
    //     fieldPath,
    //     schema,
    //   );
    // }

    const convertProps = (
      fieldPath: string,
      props: Record<string, any>,
      dcExtParams: Record<string, any>,
    ): any => {
      if (!props) {
        return undefined;
      }
      // 支持 props 为 JSExpression 的转换
      if (isJSExpression(props as any)) {
        return generateJsExpression(fieldPath, props, dcExtParams, props);
      }

      const convertPropsTemp: Record<string, any> = {};
      lodash.entries(props).forEach(([key, value]) => {
        const childFieldPath = `${fieldPath}.${key}`;
        if (key === 'dcExtParams') {
          return;
        }

        // 数组处理
        if (lodash.isArray(value)) {
          convertPropsTemp[key] = value.map((it) => {
            if (!lodash.isObject(it)) {
              return it;
            }
            return convertProps(childFieldPath, it, dcExtParams);
          });
          return;
        }
        // 对象处理
        if (lodash.isObject(value)) {
          const childValue = value as any;
          if (isJSFunction(childValue)) {
            convertPropsTemp[key] = generateJsFunction(
              childFieldPath,
              childValue,
              dcExtParams,
              props,
            );
            return;
          }
          if (isJSExpression(childValue)) {
            const ___props = generateJsExpression(
              childFieldPath,
              childValue,
              dcExtParams,
              props,
            );
            if (key === '___props') {
              lodash.entries(___props).forEach(([key, value]) => {
                convertPropsTemp[key] = value;
              });
              return;
            }
            convertPropsTemp[key] = ___props;
            return;
          }
          if (isJSSlot(childValue)) {
            convertPropsTemp[key] = generateJsSlot(
              childFieldPath,
              childValue,
              dcExtParams,
            );
            return;
          }

          convertPropsTemp[key] = convertProps(
            childFieldPath,
            value,
            dcExtParams,
          );
          return;
        }
        // 普通字段不处理
        convertPropsTemp[key] = value;
      });

      return convertPropsTemp;
    };

    // 1. 转换 props 结束
    const propsOfConvertBasic = convertProps(
      `${fieldPath}.props`,
      propsProps,
      dcExtParams,
    );
    // 设置 stateProps
    const props = { ...propsOfConvertBasic };

    // // 2. 渲染 children 开始
    // const childrenRender = (
    //   fieldPath: string,
    //   schema: undefined | DynamicSchema | DynamicSchema[],
    // ): any =>
    //   !schema
    //     ? null
    //     : ddeRender.reactNodeRender(token, fieldPath, schema, dcExtParams);

    // const children = childrenRender(`${fieldPath}.children`, childrenSchema);
    // // 2. 渲染 children 结束
    //
    // // 9. 重新组装 props
    // const props = children ? { ...propsOfConvert, children } : propsOfConvert;
    console.debug('[DC] 二、渲染的Props:', key, props, restProps);
    const domRestProps = lodash.omitBy(restProps, (_, key) => {
      return key.startsWith(`_`);
    });

    return lodash.mergeWith(props, domRestProps, (objValue, srcValue) => {
      // React 组件
      if (React.isValidElement(srcValue)) {
        return srcValue;
      }
      // {foo:bar} 简单对象
      if (lodash.isPlainObject(srcValue)) {
        return { ...objValue, ...srcValue };
      }
      return srcValue;
    });
  },
  reactNodeRender: (
    token: GlobalToken,
    fieldPath: string,
    schema: Record<string, any>,
    dcExtParams: Record<string, any>,
    restProps?: Record<string, any> | any,
    key?: string,
  ): any => {
    try {
      const {
        componentName,
        props: propsProps,
        children: childrenSchema,
      } = schema;

      if (lodash.isArray(schema)) {
        return (schema as DynamicSchema[]).map((it, idx) => {
          const props = it.props || {};
          return ddeRender.reactNodeRender(
            token,
            fieldPath,
            // { ...it, props: { ...props, key: `key-${idx}` } },
            { ...it, props: { ...lodash.omit(props, 'key') } },
            dcExtParams,
            undefined,
            `key-${idx}`,
          );
        });
      }

      // 支持 Typography.Title 情况
      const componentNamePath = componentName.split('.');
      const Component = lodash.get(Components, componentNamePath);
      if (!Component) {
        throw new ReactNodeRenderError(
          `未找到组件 ${componentName}`,
          fieldPath,
          schema,
        );
      }

      // 9. 重新组装 props
      const propsOfConvert = ddeRender.reactPropsRender(
        token,
        fieldPath,
        propsProps,
        dcExtParams,
        restProps,
        key,
      );

      // 2. 渲染 children 开始
      const childrenRender = (
        fieldPath: string,
        schema: undefined | DynamicSchema | DynamicSchema[],
      ): any => {
        return !schema
          ? null
          : ddeRender.reactNodeRender(token, fieldPath, schema, dcExtParams);
      };

      const children = childrenRender(`${fieldPath}.children`, childrenSchema);
      // 2. 渲染 children 结束

      // 9. 重新组装 props
      const props = children ? { ...propsOfConvert, children } : propsOfConvert;

      return <Component key={key} {...props} />;
    } catch (e: any) {
      if (fieldPath === `$`) {
        throw e;
      }
      return (
        <Popover
          content={
            <Result
              status="error"
              title={
                // `字段：${e.fieldPath}，错误：${e.message}`
                <>
                  字段：
                  <Typography.Text
                    style={{ fontSize: 'inherit' }}
                    code
                    copyable
                  >
                    {e.fieldPath}
                  </Typography.Text>
                  <br />
                  <Typography.Text
                    style={{ fontSize: 'inherit' }}
                    type="danger"
                  >
                    {e.message}
                  </Typography.Text>
                </>
              }
              subTitle={
                <Typography.Text type={'secondary'} copyable>
                  {lodash.isObject(e.schema)
                    ? JSON.stringify(e.schema)
                    : lodash.toString(e.schema)}
                </Typography.Text>
              }
            />
          }
        >
          <CloseCircleFilled
            style={{
              fontSize: 'inherit',
              color: token.colorError,
            }}
          />
        </Popover>
      );
    }
  },
};

export default ddeRender;
